// complement funny nucleotides
//  letter	means					complemented by
//  A		A						T		
//  C		C						G		
//  G		G						C		
//  T/U		T						A		
//  M		A or C					K		
//  R		A or G					Y		
//  W		A or T					W		
//  S		C or G					S		
//  Y		C or T					R		
//  K		G or T					M		
//  V		A or C or G				B		
//  H		A or C or T				D		
//  D		A or G or T				H		
//  B		C or G or T				V		
//  N		G or A or T or C		N




#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>


// components
#define _MY_STL_MACROS
#include <stlhelper.h>
//#define DEBUG_LEVEL 11
#include <debug_func.h>

using std::transform;
using std::string;
using std::cerr;


#include "cdna_prot.h"






namespace cdna_prot
{












//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	complement cdna

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
struct t_complement_cdna_index
{
	static char table [256];
	static bool initialized;
	char operator ()(char c) const { return table[to_uint(c)];}
};


// static data
char t_complement_cdna_index::table[256];
bool t_complement_cdna_index::initialized = false;

void init_t_complement_cdna_index_table()
{
	debug_func_cerr(5);
	// only initialize once
	if (t_complement_cdna_index::initialized)
		return;
	t_complement_cdna_index::initialized = true;
	debug_cerr(5, "Initialize conversion buffer");

	// do not change most things
	for (unsigned i = 0; i < 256; ++i)
		t_complement_cdna_index::table[i] = i;

	// ACGT
	t_complement_cdna_index::table[to_uint(DNA_A)] = DNA_T;
	t_complement_cdna_index::table[to_uint(DNA_C)] = DNA_G;
	t_complement_cdna_index::table[to_uint(DNA_G)] = DNA_C;
	t_complement_cdna_index::table[to_uint(DNA_T)] = DNA_A;
//	t_complement_cdna_index::table[to_uint(DNA_N)] = DNA_N;
//	t_complement_cdna_index::table[to_uint(DNA_R)] = DNA_Y;
//	t_complement_cdna_index::table[to_uint(DNA_Y)] = DNA_R;
//	t_complement_cdna_index::table[to_uint(DNA_K)] = DNA_M;
//	t_complement_cdna_index::table[to_uint(DNA_M)] = DNA_K;
//	t_complement_cdna_index::table[to_uint(DNA_S)] = DNA_W;
//	t_complement_cdna_index::table[to_uint(DNA_W)] = DNA_S;
//	t_complement_cdna_index::table[to_uint(DNA_B)] = DNA_V;
//	t_complement_cdna_index::table[to_uint(DNA_V)] = DNA_B;
//	t_complement_cdna_index::table[to_uint(DNA_D)] = DNA_H;
//	t_complement_cdna_index::table[to_uint(DNA_H)] = DNA_D;
}

// wrapping function
void complement_cdna_index(string& cdna)
{
	transform(cdna.begin(), cdna.end(), cdna.begin(), t_complement_cdna_index());
}

















//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	validate cdna and convert to index

//		ambiguity characters => N

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
struct t_validate_cdna_to_index
{
	static char table [256];
	static bool initialized;
	char operator ()(char c) const { return table[to_uint(c)];}
};

// static data
bool t_validate_cdna_to_index::initialized = false;
char t_validate_cdna_to_index::table[256];

void init_t_validate_cdna_to_index_table()
{
	debug_func_cerr(5);

	// only initialize once
	if (t_validate_cdna_to_index::initialized)
		return;
	t_validate_cdna_to_index::initialized = true;
	debug_cerr(5, "Initialize conversion buffer");

	// null values will be ignored
	for (unsigned i = 0; i < 256; ++i)
		t_validate_cdna_to_index::table[i] = 0;
	
	// ACGT
	t_validate_cdna_to_index::table[to_uint('A')] = DNA_A;
	t_validate_cdna_to_index::table[to_uint('C')] = DNA_C;
	t_validate_cdna_to_index::table[to_uint('G')] = DNA_G;
	t_validate_cdna_to_index::table[to_uint('T')] = DNA_T;

//	t_validate_cdna_to_index::table[to_uint('Y')] = DNA_Y;
//	t_validate_cdna_to_index::table[to_uint('R')] = DNA_R;
//	t_validate_cdna_to_index::table[to_uint('M')] = DNA_M;
//	t_validate_cdna_to_index::table[to_uint('K')] = DNA_K;
//	t_validate_cdna_to_index::table[to_uint('W')] = DNA_W;
//	t_validate_cdna_to_index::table[to_uint('S')] = DNA_S;
//	t_validate_cdna_to_index::table[to_uint('V')] = DNA_V;
//	t_validate_cdna_to_index::table[to_uint('B')] = DNA_B;
//	t_validate_cdna_to_index::table[to_uint('H')] = DNA_H;
//	t_validate_cdna_to_index::table[to_uint('D')] = DNA_D;

	// N
	string Ns = "MRWSYKVHDBN";
	for (unsigned i = 0; i < Ns.length(); ++i)
		t_validate_cdna_to_index::table[to_uint(Ns[i])] = DNA_N;


	// convert lowercase to uppercase equivalents
	for (char c = 'a'; c <= 'z'; ++c)
		t_validate_cdna_to_index::table[to_uint(c)] = t_validate_cdna_to_index::table[to_uint(c - 'a' + 'A')];
	//for (unsigned i = 0; i < 256; ++i)
	//	cerr << "table[" << i << "]=" << (unsigned) t_validate_cdna_to_index::table[i] << "\n";
}





// wrapping function
void validate_cdna_to_index(string& sequence)
{
	debug_func_cerr(5);
	sequence.erase(transform_if(sequence.begin(), sequence.end(), sequence.begin(),
								t_validate_cdna_to_index()), sequence.end());
}



















//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	validate prot

//		ambiguity characters => N

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
t_validate_prot::t_validate_prot(string letters, bool upper_case, bool asterisk_is_X,
						bool letters_are_valid)
{
	debug_func_cerr(5);

	if (letters_are_valid && letters.length())
		// null values will be ignored
		for (unsigned i = 0; i < 256; ++i)
			table[i] = 0;
	else
		for (unsigned i = 0; i < 256; ++i)
			table[i] = char(i);

	// no validation
	if (letters.length() == 0)
	{
		debug_cerr(5, "valid letters zero length");
		asterisk_is_X = false;
		return;
	}


	// make each letter in letters point to itself
	if (letters_are_valid)
		for (unsigned i = 0; i < letters.length(); ++i)
			table[to_uint(letters[i])] = letters[i];

	if (asterisk_is_X)
		table[to_uint('*')] = 'X';

	// if upper_case
	//		all corresponding uppercase of valid letters should point to self
	//		all lowercase letters should point to what uppercase points to
	if (upper_case)
	{
		for (unsigned c = 'a'; c <= 'z'; ++c)
		{
			// change all valid amino acid letters to uppercase
			if (table[c])
				table[c - 'a' + 'A'] = c - 'a' + 'A';
			// lowercase lookup points to what uppercase lookup points to
			table[c] = table[c - 'a' + 'A'];
		}
	}

	// make each invalide letter in letters point to 0
	if (!letters_are_valid)
		for (unsigned i = 0; i < letters.length(); ++i)
			table[to_uint(letters[i])] = 0;
}
























//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	index to cdna

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
struct t_cdna_index_to_text
{
	static char table [7];
	static bool initialized;
	char operator ()(char c) const { return table[to_uint(c)];}
};

// static data
bool t_cdna_index_to_text::initialized = false;
char t_cdna_index_to_text::table[7];

void init_t_cdna_index_to_text_table()
{
	debug_func_cerr(5);

	// only initialize once
	if (t_cdna_index_to_text::initialized)
		return;
	t_cdna_index_to_text::initialized = true;
	debug_cerr(5, "Initialize conversion buffer");

	char buf[] = "-ACGTN^";
	std::copy(buf, buf + 7, t_cdna_index_to_text::table);
}



// wrapping function
string cdna_index_to_text(string sequence)
{
	transform(sequence.begin(), sequence.end(), sequence.begin(), t_cdna_index_to_text());
	return sequence;
}







































//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	cdna to protein

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
// static data
bool t_cdna_index_to_prot::initialized = false;
char t_cdna_index_to_prot::table [6][6][6];

//____________________________________________________________________________________
// cdna to prot conversion helpers
//____________________________________________________________________________________
void degenerate_conversion(unsigned codon_pos1, unsigned codon_pos2, char prot)
{
	for (unsigned i = 1; i <=5; ++i)
		t_cdna_index_to_prot::table[codon_pos1][codon_pos2][i] = prot;
}
void degenerate_conversion_ct(unsigned codon_pos1, unsigned codon_pos2, char prot)
{
	t_cdna_index_to_prot::table[codon_pos1][codon_pos2][2] =
	t_cdna_index_to_prot::table[codon_pos1][codon_pos2][4] = prot;
}
void degenerate_conversion_ag(unsigned codon_pos1, unsigned codon_pos2, char prot)
{
	t_cdna_index_to_prot::table[codon_pos1][codon_pos2][1] =
	t_cdna_index_to_prot::table[codon_pos1][codon_pos2][3] = prot;
}


void init_t_cdna_index_to_prot_table()
{
	debug_func_cerr(5);

	// only initialize once
	if (t_cdna_index_to_prot::initialized)
		return;
	t_cdna_index_to_prot::initialized = true;
	debug_cerr(5, "Initialize conversion buffer");

	// unknown codons = 'X'
	std::memset(t_cdna_index_to_prot::table, 'X', sizeof t_cdna_index_to_prot::table);

	// stop codons = '*'
	t_cdna_index_to_prot::table[to_uint(DNA_T)][to_uint(DNA_G)][to_uint(DNA_A)] = '*';
	degenerate_conversion_ag(	DNA_T,DNA_A, '*');

	degenerate_conversion(		DNA_A,DNA_C, 'T');
	degenerate_conversion(		DNA_C,DNA_C, 'P');
	degenerate_conversion(		DNA_C,DNA_G, 'R');
	degenerate_conversion(		DNA_C,DNA_T, 'L');
	degenerate_conversion(		DNA_G,DNA_C, 'A');
	degenerate_conversion(		DNA_G,DNA_G, 'G');
	degenerate_conversion(		DNA_G,DNA_T, 'V');
	degenerate_conversion(		DNA_T,DNA_C, 'S');

	degenerate_conversion_ct(	DNA_A,DNA_A, 'N');
	degenerate_conversion_ct(	DNA_A,DNA_G, 'S');
	degenerate_conversion_ct(	DNA_A,DNA_T, 'I');
	degenerate_conversion_ct(	DNA_C,DNA_A, 'H');
	degenerate_conversion_ct(	DNA_G,DNA_A, 'D');
	degenerate_conversion_ct(	DNA_T,DNA_A, 'Y');
	degenerate_conversion_ct(	DNA_T,DNA_G, 'C');
	degenerate_conversion_ct(	DNA_T,DNA_T, 'F');

	degenerate_conversion_ag(	DNA_A,DNA_A, 'K');
	degenerate_conversion_ag(	DNA_A,DNA_G, 'R');
	degenerate_conversion_ag(	DNA_C,DNA_A, 'Q');
	degenerate_conversion_ag(	DNA_G,DNA_A, 'E');
	degenerate_conversion_ag(	DNA_T,DNA_T, 'L');
	t_cdna_index_to_prot::table[to_uint(DNA_A)][to_uint(DNA_T)][to_uint(DNA_A)] =	'I';
	t_cdna_index_to_prot::table[to_uint(DNA_A)][to_uint(DNA_T)][to_uint(DNA_G)] =	'M';
	t_cdna_index_to_prot::table[to_uint(DNA_T)][to_uint(DNA_G)][to_uint(DNA_G)] =	'W';

}



// wrapping function
std::string cdna_index_to_prot(const std::string& cdna)
{
	const char* p_cdna = cdna.c_str();
	string ret;
	unsigned prot_len  = cdna.length() / 3;
	ret.reserve(prot_len);
	const char* end = p_cdna + prot_len * 3;
	for (;p_cdna < end; p_cdna += 3)
	{
//		cerr << "[" << (unsigned)*(p_cdna+0) << "]"
//			 << "[" << (unsigned)*(p_cdna+1) << "]"
//			 << "[" << (unsigned)*(p_cdna+2) << "]"
//			 << t_cdna_index_to_text()(*(p_cdna+0))
//			<< t_cdna_index_to_text()(*(p_cdna+1))
//			<< t_cdna_index_to_text()(*(p_cdna+2))
//			<< "="
//			<< t_cdna_index_to_prot()(p_cdna) << "\n";
		ret += t_cdna_index_to_prot()(p_cdna);
	}
	return ret;
}























//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	initialize conversion buffers

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
void initialize_conversion_buffers()
{
	init_t_complement_cdna_index_table();
	init_t_validate_cdna_to_index_table();
	init_t_cdna_index_to_text_table();
	init_t_cdna_index_to_prot_table();
}



}
